﻿using OperationPlugins.Reflections;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Linq;

namespace OperationPlugins.Compositions
{
    public static class CompositionContainerExtensions
    {
        private static IEnumerable<IAuthorizePlugin> Authorization { get; set; }

        /// <summary>
        /// Gets the plugin.
        /// </summary>
        /// <typeparam name="T">The type of the plugin object to return.</typeparam>
        /// <returns>The plugin object, or null if not found.</returns>
        public static T GetPlugin<T>(this CompositionContainer container)
        {
            return Get<T>(container).FirstOrDefault();
        }

        /// <summary>
        /// Gets all plugins.
        /// </summary>
        /// <typeparam name="T">The type of the plugin objects to return.</typeparam>
        /// <returns>Plugin objects, or an empty collection if not found.></returns>
        public static IEnumerable<T> GetPlugins<T>(this CompositionContainer container)
        {
            return Get<T>(container);
        }

        internal static IEnumerable<T> Get<T>(CompositionContainer container)
        {
            var l = new List<T>();

            var priorityNumbers = new Dictionary<double, T>();

            T[] plugins = container.GetExportedValues<T>().ToArray();

            foreach (T plugin in plugins)
            {
                Type pluginType = plugin.GetType();

                // Should this plugin be ignored?
                // All plugins with IgnoreAttribute will be ignored!
                bool ignore = pluginType.HasAttribute<IgnoreAttribute>();
                if (ignore)
                {
                    continue;
                }

                // Is it authorized to invoke this plugin?
                bool authorized = true;
                if (Authorization != null)
                {
                    foreach (var export in Authorization)
                    {
                        authorized = export.Authorize(pluginType);
                        if (authorized == false)
                        {
                            break;
                        }
                    }
                }

                if (authorized == false)
                {
                    continue;
                }

                // If the plugin have a PriorityAttribute, then make sure that these priority number are unique!
                var priority = pluginType.GetAttribute<PriorityAttribute>();
                if (priority != null)
                {
                    double number = priority.Number;

                    if (priorityNumbers.ContainsKey(number) == false)
                    {
                        priorityNumbers.Add(number, plugin);
                    }
                    else
                    {
                        throw new AmbiguousPriorityException(
                            string.Format("At least two plugins have the same priority number ({0}). These plugins are {1} and {2}. The priority number must be unique.",
                                          number,
                                          priorityNumbers[number],
                                          pluginType));
                    }
                }

                l.Add(plugin);
            }

            // Sorts the list by priority number.
            l.Sort(delegate(T plugin1, T plugin2)
            {
                var p1 = plugin1.GetType().GetAttribute<PriorityAttribute>();
                var n1 = p1 != null ? p1.Number : DefaultPriorityAttribute.Value + 1;

                var p2 = plugin2.GetType().GetAttribute<PriorityAttribute>();
                var n2 = p2 != null ? p2.Number : DefaultPriorityAttribute.Value + 1;

                return n1.CompareTo(n2);
            });

            return l.ToArray();
        }

        /// <summary>
        /// Initialize. 
        /// </summary>
        [Export(typeof(IInitialize))]
        private sealed class Initializer : IInitialize
        {
            void IInitialize.Initialize(InitializationContext context)
            {
                Authorization = context.CompositionContainer.GetPlugins<IAuthorizePlugin>();
            }
        }

    }
}
